package com.example.distributedemo.service;

import com.example.distributedemo.dao.OrderItemMapper;
import com.example.distributedemo.dao.OrderMapper;
import com.example.distributedemo.dao.ProductMapper;
import com.example.distributedemo.model.Order;
import com.example.distributedemo.model.OrderItem;
import com.example.distributedemo.model.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class OrderService {

    @Resource
    private OrderMapper orderMapper;
    @Resource
    private OrderItemMapper orderItemMapper;
    @Resource
    private ProductMapper productMapper;
    //购买商品id
    private int purchaseProductId = 100100;
    //购买商品数量
    private int purchaseProductNum = 1;
    // 平台的事务管理器
    @Autowired
    private PlatformTransactionManager platformTransactionManager;
    // 事务的定义
    @Autowired
    private TransactionDefinition transactionDefinition;



    /**
     * 模拟一个创建订单的过程
     * 这边加上一个事务，因为修改了三张表的数据，
     * 所以要确保这三个动作在同一个事务当中
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public Integer createOrder() throws Exception{


        Product product = productMapper.selectByPrimaryKey(purchaseProductId);
        if (product==null){
            throw new Exception("购买商品："+purchaseProductId+"不存在");
        }

        //商品当前库存
        Integer currentCount = product.getCount();

        //校验库存 , 若当前购买的数量大于商品库存的数量，抛出异常
        if (purchaseProductNum > currentCount){
            throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件，无法购买");
        }
        // 计算剩余库存
        Integer leftCount = currentCount - purchaseProductNum;
        // 更新库存
        product.setCount(leftCount);
        product.setUpdateTime(new Date());
        product.setUpdateUser("xxx");
        productMapper.updateByPrimaryKey(product);


        // 创建一笔订单，存入数据库
        Order order = new Order();
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
        order.setOrderStatus(1);//待处理
        order.setReceiverName("xxx");
        order.setReceiverMobile("13311112222");
        order.setCreateTime(new Date());
        order.setCreateUser("xxx");
        order.setUpdateTime(new Date());
        order.setUpdateUser("xxx");
        orderMapper.insertSelective(order);

        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(purchaseProductNum);
        orderItem.setCreateUser("xxx");
        orderItem.setCreateTime(new Date());
        orderItem.setUpdateTime(new Date());
        orderItem.setUpdateUser("xxx");
        orderItemMapper.insertSelective(orderItem);
        return order.getId();

    }

    /**
     * 利用Mysql的增量Update来更新库存
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public Integer createOrder1() throws Exception{


        Product product = productMapper.selectByPrimaryKey(purchaseProductId);
        if (product==null){
            throw new Exception("购买商品："+purchaseProductId+"不存在");
        }

        //商品当前库存
        Integer currentCount = product.getCount();

        //校验库存 , 若当前购买的数量大于商品库存的数量，抛出异常
        if (purchaseProductNum > currentCount){
            throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件，无法购买");
        }

        /**
         * 使用数据库的 update 来更新库存 （使用增量的方式）
         * 利用Mysql update的行锁来保证多并发的情况下安全,
         * 这边因为SQL语句没有对 库存数量进行校验（库存数量的校验在上面的代码中）
         * 所以会导致库存数量 扣成负数的情况。
         */
        productMapper.updateProductCount(purchaseProductNum,
                                    "xxx",new Date(),
                                    product.getId());
        /*
         解决办法：
         检索商品库存，若商品库存为负数，就抛出异常,更新操作就会回滚了 （@Transactional)
         */

        // 创建一笔订单，存入数据库
        Order order = new Order();
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
        order.setOrderStatus(1);//待处理
        order.setReceiverName("xxx");
        order.setReceiverMobile("13311112222");
        order.setCreateTime(new Date());
        order.setCreateUser("xxx");
        order.setUpdateTime(new Date());
        order.setUpdateUser("xxx");
        orderMapper.insertSelective(order);

        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(purchaseProductNum);
        orderItem.setCreateUser("xxx");
        orderItem.setCreateTime(new Date());
        orderItem.setUpdateTime(new Date());
        orderItem.setUpdateUser("xxx");
        orderItemMapper.insertSelective(orderItem);
        return order.getId();
    }

    /**
     * 利用 Synchronized锁来解决超卖问题
     * 可以给整个方法加锁
     * 也可以给代码块加锁
     * 注意点： 事务和锁配合使用时，可能会出现如下情况：
     *  线程A执行完方法，释放锁，但此时的事务还没有提交，数据库中的库存还未更改为0
     *  线程B获取到锁，执行方法，此时查询得到数据库中商品的库存为1
     *  线程A的事务提交，数据库中商品的库存更改为0.
     *  线程B执行库存更新操作，数据库中商品的库存修改为-1
     *  （需要避免这种情况的产生，锁释放之前执行事务 锁住事务
     *      手动控制事务
     *  ）
     * @return
     * @throws Exception
     */
    //@Transactional(rollbackFor = Exception.class)
    public synchronized Integer createOrder2() throws Exception{
        Product product = null;
        // 获取事务
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);

        product = productMapper.selectByPrimaryKey(purchaseProductId);
        if (product == null) {
            // 事务的回滚
            platformTransactionManager.rollback(transaction);
            throw new Exception("购买商品：" + purchaseProductId + "不存在");
        }

        //商品当前库存
        Integer currentCount = product.getCount();
        // 输出每个线程的库存数
        System.out.println(Thread.currentThread().getName() + "库存数" + currentCount);

        //校验库存 , 若当前购买的数量大于商品库存的数量，抛出异常
        if (purchaseProductNum > currentCount) {
            // 事务的回滚
            platformTransactionManager.rollback(transaction);
            throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件，无法购买");
        }
        /**
         * 使用数据库的 update 来更新库存 （使用增量的方式）
         * 利用Mysql update的行锁来保证多并发的情况下安全,
         * 这边因为SQL语句没有对 库存数量进行校验（库存数量的校验在上面的代码中）
         * 所以会导致库存数量 扣成负数的情况。
         */
        productMapper.updateProductCount(purchaseProductNum,
                "xxx", new Date(),
                product.getId());

        // 创建一笔订单，存入数据库
        Order order = new Order();
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
        order.setOrderStatus(1);//待处理
        order.setReceiverName("xxx");
        order.setReceiverMobile("13311112222");
        order.setCreateTime(new Date());
        order.setCreateUser("xxx");
        order.setUpdateTime(new Date());
        order.setUpdateUser("xxx");
        orderMapper.insertSelective(order);

        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(purchaseProductNum);
        orderItem.setCreateUser("xxx");
        orderItem.setCreateTime(new Date());
        orderItem.setUpdateTime(new Date());
        orderItem.setUpdateUser("xxx");
        orderItemMapper.insertSelective(orderItem);
        // 手动提交事务
        platformTransactionManager.commit(transaction);
        return order.getId();
    }


    private ReentrantLock lock = new ReentrantLock();

    /**
     * 使用 ReentrantLock 锁来解决 超卖问题
     * @return
     * @throws Exception
     */
    public synchronized Integer createOrder3() throws Exception{
        Product product = null;
        // 加锁
        lock.lock();
        try{
            // 获取事务
            TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);

            product = productMapper.selectByPrimaryKey(purchaseProductId);
            if (product==null){
                // 事务的回滚
                platformTransactionManager.rollback(transaction);
                throw new Exception("购买商品："+purchaseProductId+"不存在");
            }

            //商品当前库存
            Integer currentCount = product.getCount();
            // 输出每个线程的库存数
            System.out.println(Thread.currentThread().getName()+ "库存数"+currentCount);

            //校验库存 , 若当前购买的数量大于商品库存的数量，抛出异常
            if (purchaseProductNum > currentCount){
                // 事务的回滚
                platformTransactionManager.rollback(transaction);
                throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件，无法购买");
            }
            /**
             * 使用数据库的 update 来更新库存 （使用增量的方式）
             * 利用Mysql update的行锁来保证多并发的情况下安全,
             * 这边因为SQL语句没有对 库存数量进行校验（库存数量的校验在上面的代码中）
             * 所以会导致库存数量 扣成负数的情况。
             */
            productMapper.updateProductCount(purchaseProductNum,
                    "xxx",new Date(),
                    product.getId());
            platformTransactionManager.commit(transaction);
        }finally {
            // 抛出异常，锁是不会被释放的
            // 不管如何都需要释放锁
            lock.unlock();
        }

        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
        // 创建一笔订单，存入数据库
        Order order = new Order();
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
        order.setOrderStatus(1);//待处理
        order.setReceiverName("xxx");
        order.setReceiverMobile("13311112222");
        order.setCreateTime(new Date());
        order.setCreateUser("xxx");
        order.setUpdateTime(new Date());
        order.setUpdateUser("xxx");
        orderMapper.insertSelective(order);

        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(purchaseProductNum);
        orderItem.setCreateUser("xxx");
        orderItem.setCreateTime(new Date());
        orderItem.setUpdateTime(new Date());
        orderItem.setUpdateUser("xxx");
        orderItemMapper.insertSelective(orderItem);
        // 手动提交事务
        platformTransactionManager.commit(transaction);
        return order.getId();
    }

}
